package com.es.service.impl;

import com.es.dto.BrandDTO;
import com.es.dto.CategoryDTO;
import com.es.dto.GoodsDTO;
import com.es.dto.SpecParamDTO;
import com.es.entity.CarParkFindRequest;
import com.es.entity.CountVo;
import com.es.entity.Goods;
import com.es.entity.MySearchRequest;
import com.es.repository.MyElasticsearch;
import com.es.service.GoodsService;
import com.es.service.TbBrandService;
import com.es.service.TbCategoryService;
import com.es.service.TbSpecParamService;
import com.es.utils.BeanHelper;
import com.es.entity.NewCarPark;
import com.es.service.SearchService;
import com.es.utils.PageResult;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Copyright
 *
 * @Author WuHuang
 * @Date 2022/5/24
 */
@Service
public class SearchServiceImpl implements SearchService {
    /**
     * 附近停车场范围  （公里）
     */
    private static final double DISTANCE = 3;

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private TbBrandService tbBrandService;

    @Autowired
    private MyElasticsearch myElasticsearch;

    @Autowired
    private TbSpecParamService specParamService;

    @Autowired
    private TbCategoryService tbCategoryService;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Override
    public void addNew(NewCarPark newCarPark) {
        elasticsearchTemplate.createIndex(NewCarPark.class);
        elasticsearchTemplate.putMapping(NewCarPark.class);
        myElasticsearch.save(newCarPark);
    }

    @Override
    public CountVo<NewCarPark> find(CarParkFindRequest request) {
        boolean indexExists = elasticsearchTemplate.indexExists(NewCarPark.class);
        if (!indexExists) {
            elasticsearchTemplate.createIndex(NewCarPark.class);
        }
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        // 间接实现了QueryBuilder接口
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if (StringUtils.isNotBlank(request.getSearchTerms())) {
            QueryBuilder ikNameQuery = QueryBuilders.matchQuery("name", request.getSearchTerms());
            boolQueryBuilder.must(ikNameQuery);
        } else {
            // 以某点为中心，搜索指定范围
            GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("location");
            distanceQueryBuilder.point(request.getLat(), request.getLng());
            // 定义查询单位：公里
            distanceQueryBuilder.distance(DISTANCE, DistanceUnit.KILOMETERS);
            boolQueryBuilder.filter(distanceQueryBuilder);
        }

        searchQueryBuilder.withQuery(boolQueryBuilder);
        GeoDistanceSortBuilder location = SortBuilders
                .geoDistanceSort("location", request.getLat(), request.getLng())
                .unit(DistanceUnit.MILES)
                .order(SortOrder.ASC);
        searchQueryBuilder.withSort(location);
        // 分页
        if (request.getPage() != null) {
            searchQueryBuilder.withPageable(PageRequest.of(request.getPage() - 1, request.getPageSize() == null ? 10 : request.getPageSize()));
        }
        Page<NewCarPark> search = myElasticsearch.search(searchQueryBuilder.build());
        CountVo<NewCarPark> countVo = new CountVo<>();
        countVo.setCount((int) search.getTotalElements());
        countVo.setList(search.getContent());
        return countVo;
    }

    /**
     * 简单搜索
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> search(MySearchRequest request) {
        //用户输入的关键词
        String key = request.getKey();
        //创建原生的查询器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加结果的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[] {"id","subTitle","skus"},null));
        //添加搜索的关键字
        queryBuilder.withQuery(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        //添加分页信息
        queryBuilder.withPageable(PageRequest.of(request.getPage() -1 ,request.getSize()));
        //把查询的内容发送到es的服务端
        AggregatedPage<Goods> aggregatedPage = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //处理结果
        List<Goods> goodsList = aggregatedPage.getContent();
        if(CollectionUtils.isEmpty(goodsList)){
            throw new IndexOutOfBoundsException();
        }
        List<GoodsDTO> goodsDTOS = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);
        return new PageResult<GoodsDTO>(goodsDTOS,aggregatedPage.getTotalElements(),(long)aggregatedPage.getTotalPages());
    }


    /**
     *  查询过滤的条件
     * @param request
     * @return
     */
    @Override
    public Map<String, List<?>> searchFilter(MySearchRequest request) {
        //创建过滤项集合
        Map<String,List<?>> filterMap = new LinkedHashMap<>();
        //创建原生的查询器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加返回结果的过滤
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //添加查询的关键字
        String key = request.getKey();
        queryBuilder.withQuery(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        // 添加分页的配置
        queryBuilder.withPageable(PageRequest.of(0,1));
        //添加分类的聚合配置
        String categoryAggName = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));
        //添加品牌的聚合配置
        String brandAggName="brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        //发送查询请求 ,并获取返回值
        AggregatedPage<Goods> aggregatedPage = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //获取 聚合的结果
        Aggregations aggregations = aggregatedPage.getAggregations();
        //获取 分类的聚合结果
        LongTerms categoryAgg = aggregations.get(categoryAggName);
        List<Long> ids = handlerCategoryName(categoryAgg,filterMap);
        //获取 品牌的聚合结果
        LongTerms brandAgg = aggregations.get(brandAggName);
        handlerBrandName(brandAgg,filterMap);
        //获取规格参数的聚合
        //当分类的聚合只有一个时
        if(!CollectionUtils.isEmpty(ids) && ids.size() == 1){
            handlerSpecAgg(ids.get(0),request,filterMap);
        }
        return filterMap;
    }

    /**
     * 获取规格参数的聚合
     * @param ids
     * @param request
     * @param filterMap
     */
    private void handlerSpecAgg(Long ids, MySearchRequest request, Map<String, List<?>> filterMap) {
        //获取分类对应的规格参数
        List<SpecParamDTO> paramDTOS = specParamService.findSpecParamByGid(null,ids,true);
        //创建原生查询器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //设置结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        //设置关键词搜索
        String key = request.getKey();
        queryBuilder.withQuery(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        //设置分页
        queryBuilder.withPageable(PageRequest.of(0,1));
        //设置规格参数聚合
        for (SpecParamDTO paramDTO : paramDTOS) {
            String name = paramDTO.getName();
            String fieldName = "specs."+name;
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field(fieldName));
        }
        //发送查询请求
        AggregatedPage<Goods> aggregatedPage = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //处理结果,得到聚合结果
        Aggregations aggregations = aggregatedPage.getAggregations();
        for (SpecParamDTO paramDTO : paramDTOS) {
            String name = paramDTO.getName();
            //获取规格参数的结果
            StringTerms specAgg = aggregations.get(name);
            //从结果获取buckets
            List<String> specAggResult = specAgg.getBuckets().stream()
                    .map(StringTerms.Bucket::getKeyAsString)
                    .collect(Collectors.toList());
            //那聚合的内容放入Map里边
            filterMap.put(name,specAggResult);
        }
    }

    /**
     * 获取品牌的名字
     * @param brandAgg
     * @param filterMap
     */
    private void handlerBrandName(LongTerms brandAgg, Map<String, List<?>> filterMap) {
        List<LongTerms.Bucket> buckets = brandAgg.getBuckets();
        List<Long> branIdList = buckets.stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //查询品牌的信息
        List<BrandDTO> brandDTOList = tbBrandService.findBrandListByIds(branIdList);
        //把查询到的品牌信息存到Map里边
        filterMap.put("品牌",brandDTOList);
    }

    /**
     * 获取分类的名字
     * @param categoryAgg
     * @param filterMap
     */
    private List<Long> handlerCategoryName(LongTerms categoryAgg, Map<String, List<?>> filterMap) {
        List<LongTerms.Bucket> buckets = categoryAgg.getBuckets();
        List<Long> categorIdList = buckets.stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //获取分类的对象信息
        List<CategoryDTO> categoryDTOList = tbCategoryService.findCategoryListByIds(categorIdList);
        //把分类信息 放入到Map中
        filterMap.put("分类",categoryDTOList);
        return categorIdList;
    }



    public QueryBuilder basicQuery(MySearchRequest request){
        String key = request.getKey();
        //创建bool查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //must条件
        boolQuery.must(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        //获取用户的过滤项
        Map<String, String> filter = request.getFilter();
        if(!CollectionUtils.isEmpty(filter)){
            for (String name : filter.keySet()) {
                String fieldName = "specs."+ name;
                if(name.equals("分类")){
                    fieldName = "categoryId";
                }else if(name.equals("品牌")){
                    fieldName = "brandId";
                }
                String val = filter.get(name);
                boolQuery.filter(QueryBuilders.termQuery(fieldName,val));
            }
        }
        return boolQuery;
    }
}
